﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.Text;
using System.Web;

namespace NSuggest.Jsonp.Core
{
	public abstract class JsonpHandler : IHttpHandler
	{

		protected string CallbackParameterKey {get; set;}

		protected JsonpHandler(string callbackParameterKey)
		{
			CallbackParameterKey = callbackParameterKey;
		}

		protected JsonpHandler() : this("callback") {}

		// implement in subclass
		protected abstract Boolean IsAuthorized(HttpContext context);
		protected abstract object GetObject(HttpContext context);

		public void ProcessRequest(HttpContext context)
		{
			// Check that request is authorized
			if (!IsAuthorized(context))
			{

				HandleError(context, 401, "400 Unauthorized", "Not authorized to use this service");
				return;
			}

			// check that callback is given in query string
			var callback = context.Request.QueryString[CallbackParameterKey];
			if (string.IsNullOrEmpty(callback))
			{
				HandleError(context, 400, "400 Bad Request",
							string.Format("Callback parameter ({0})not provided", CallbackParameterKey));
				return;
			}

			try
			{
				// Get result from business logic
				var result = GetObject(context); // may throw HttpException or Exception
				// Serialize the result to JSON
				var json = SerializeToJson(result); // may throw Exception

				// Set response headers
				context.Response.StatusCode = 200;
				context.Response.StatusDescription = "200 OK";
				context.Response.ContentType = "text/javascript";

				// Write data to response
				context.Response.Write(string.Format("{0}({1})", callback, json));
			}
			catch (InvalidDataContractException e)
			{
				HandleError(context, 500, "500 Internal Server Error", "The type being serialized does not conform to data contract rules.");
			}
			catch (SerializationException e)
			{
				HandleError(context, 500, "500 Internal Server Error", "Result could not be serialized to JSON.");
			}
			catch (QuotaExceededException e)
			{
				HandleError(context, 500, "500 Internal Server Error", "The maximum number of objects to serialize has been exceeded.");
			}
			catch (Exception exception)
			{
				// Handle other error. Return the HTTP status code.
				HandleError(context, exception);
			}
		}

		public bool IsReusable
		{
			get { throw new NotImplementedException(); }
		}

		/// <summary>
		/// Utility method for serializing object to JSON. In order to work, TAnswer must use DataContract/DataMember attributes.
		/// </summary>
		/// <param name="obj">The business object to serialize.</param>
		/// <returns></returns>
		private static string SerializeToJson(object obj)
		{
			var serializer = new DataContractJsonSerializer(obj.GetType());
			var ms = new MemoryStream();
			serializer.WriteObject(ms, obj);
			var json = Encoding.Default.GetString(ms.ToArray());
			return json;
		}

		/// <summary>
		/// If an exception occours when getting objects or when serializing them, this method is called. It will write an HTTP error to the Response.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <param name="exception">The exception.</param>
		private static void HandleError(HttpContext context, Exception exception)
		{
			var httpException = exception as HttpException;
			if (httpException != null)
			{
				context.Response.StatusCode = httpException.GetHttpCode();
				context.Response.StatusDescription = httpException.Message;
				context.Response.Write(httpException.GetHtmlErrorMessage() ?? string.Format(@"<html><head></head><body>{0}</body></html>", exception.Message));
			}
			else
			{
				context.Response.StatusCode = 500;
				context.Response.StatusDescription = "500 Internal Server Error";
				context.Response.Write(string.Format(@"<html><head></head><body>Unknown error: {0}</body></html>", exception.Message));
			}
		}

		private static void HandleError(HttpContext context, int statusCode, string statusDescription, string message)
		{
			context.Response.StatusCode = statusCode;
			context.Response.StatusDescription = statusDescription;
			context.Response.Write(string.Format(@"<html><head></head><body>Unknown error: {0}</body></html>", message));
		}
	}
}
